/*
 * Created on 2004-06-03
 */
package org.opencube.oms.mapping.generator;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.opencube.oms.meta.ComplexAttribute;
import org.opencube.oms.meta.Datatype;
import org.opencube.oms.meta.DatatypeEnumeration;
import org.opencube.oms.meta.Namespace;
import org.opencube.oms.meta.OMSMetaData;
import org.opencube.oms.meta.OMSMetaDataException;
import org.opencube.oms.meta.Scheme;
import org.opencube.oms.meta.SimpleAttribute;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author <a href="mailto:maciek@fingo.pl">FINGO - Maciej Mroczko</a>
 * TODO: comment
 */
public class XMIMetaData extends OMSMetaData
{
	private static final String EL_GENERALIZATION = "UML:Generalization";
	private static final String EL_PACKAGE = "UML:Package";
	private static final String EL_CLASS = "UML:Class";

	private static final String SCHEMES_ELEMENT = "Scheme Datatypes View";
	private static final String DATATYPES_ELEMENT = "Datatypes";
	private static final String NAMESPACE_OMSCUBE = "omscube"; 
	private static final String SCHEME_OBJECT = "Object"; 
	
	private HashMap schemesInheritance = new HashMap();
	private HashMap schemes = new HashMap();
	private HashMap namespaces = new HashMap();
	private HashMap schemeElements = new HashMap();
	private HashMap datatypes = new HashMap();
	
	private Scheme objectScheme = null;
	
	private String userId = null;

	public XMIMetaData( String docPath, String userId ) 
	throws IOException, SAXException, ParserConfigurationException, OMSMetaDataException
	{
		this.userId = userId;
		Document doc = getDocument( docPath );	
		addInheritances( doc.getElementsByTagName( EL_GENERALIZATION ) );
		addNamespaces( getSchemesPackage( doc ), null );
		addDatatypes( getDatatypesPackage( doc ).getFromFirstLevel( EL_PACKAGE, null ) );
		setInheritances();
		addAttributes();
		
	}
	
	private Document getDocument( String path )
	throws IOException, SAXException, ParserConfigurationException
	{
		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		return builder.parse( new File( path ) );
	}

	private void addAttributes() throws OMSMetaDataException
	{
		Iterator it = schemes.keySet().iterator();
		while( it.hasNext() )
		{
			String id = (String) it.next();
			Scheme scheme = (Scheme) schemes.get( id );
			if( scheme.equals( objectScheme ) )
			{
				continue;
			}
			XMIElement e = (XMIElement) schemeElements.get( id );
			Node[] atts = e.getFromFirstLevel( "UML:Attribute", null );
			for( int i = 0; i < atts.length; i++ )
			{
				try
				{
					XMIElement att = new XMIElement( atts[ i ] );
					Node[] refs = new XMIElement( atts[ i ] ).getFromFirstLevel( "UML:Classifier", null );
					if( refs != null && refs.length == 1 )
					{
						String refId = refs[ 0 ].getAttributes().getNamedItem( "xmi.idref" ).getNodeValue(); 
						Scheme refScheme = (Scheme) schemes.get( refId );
						if( refScheme != null )
						{
							int min = 0;
							int max = 0;
							try
							{
								min = Integer.parseInt( getTagValue( att, "lowerBound" ) );
							}
							catch( NumberFormatException ignore )
							{
							}
							try
							{
								max = Integer.parseInt( getTagValue( att, "upperBound" ) );
							}
							catch( NumberFormatException ignore )
							{
							}
							char associationType = 'u';
							if( getTagValue( att, "containment" ).equals( "By Reference" ) )
							{
								associationType = 'a';
							}
							else if( getTagValue( att, "containment" ).equals( "By Value" ) )
							{
								associationType = 'c';
							} 
							else
							{
								throw new OMSMetaDataException( "The 'containment' is not set for the '" + scheme.getNamespace().getPath() + "/" + scheme.getName() + "." + att.getName() + " attribute" );
							}
							
							
							ComplexAttribute cat = scheme.createComplexAttribute( refScheme, min, max, associationType, att.getId(), att.getName(), null, userId, null, userId );
						}
						else
						{
							String typeName = null;
							try
							{
								typeName = getTagValue( att, "type" );
							}
							catch( NullPointerException ex )
							{
								System.out.println( "************************ NO TYPE FOUND FOR: " + scheme.getNamespace().getPath() + "/" + scheme.getName() + "." + att.getName() );
							}
							Datatype datatype = (Datatype) datatypes.get( typeName );
							String defaultValue = null;
							// TODO get the static use = '1' from xml
							SimpleAttribute sat = scheme.createSimpleAttribute( datatype, defaultValue, '1', att.getId(), att.getName(), null, userId, null, userId );
						}
					}
				}
				catch( Exception ex )
				{
					System.out.println( "SCHEME = " + scheme.getNamespace().getPath() + "/" + scheme.getName() );
					ex.printStackTrace();
				}
			}
		}
	}
	
	private String getTagValue( XMIElement e, String tagName )
	{
		Node n = e.getByAttribute( "UML:TaggedValue", null, "tag", tagName );
		return n.getAttributes().getNamedItem( "value" ).getNodeValue();
	}
	private void addInheritances( NodeList list )
	{
		for (int i = 0; i < list.getLength(); i++)
		{
			Node n = list.item( i );
			String subtype = n.getAttributes().getNamedItem( "subtype" ).getNodeValue();
			String supertype = n.getAttributes().getNamedItem( "supertype" ).getNodeValue();
			schemesInheritance.put( subtype, supertype );
		}
	}
	
	private void setInheritances() throws OMSMetaDataException
	{
		Iterator it = schemes.keySet().iterator();
		while( it.hasNext() )
		{
			String id = (String) it.next();
			Scheme scheme = (Scheme) schemes.get( id );
			String parentId = (String) schemesInheritance.get( id );
			if( parentId != null )
			{
				Scheme parent = (Scheme) schemes.get( parentId );
				if( parent == null )
				{
					throw new OMSMetaDataException( "The scheme with id " + parentId + " not found" );
				}
				scheme.setParentScheme( parent );
			}  
			else
			{
				if( !scheme.equals( objectScheme ) )
				{
					scheme.setParentScheme( objectScheme );
					System.out.println( "NO PARENT SCHEME FOR: " + scheme.getNamespace().getPath() + "/" + scheme.getName() + " -> " + objectScheme.getNamespace().getPath() + "/" + objectScheme.getName() + " assumed" );
				}
			}
		}
	}
	private void addNamespaces( XMIElement element, Namespace parent )
	throws OMSMetaDataException
	{
		Node[] packageList = element.getFromFirstLevel( EL_PACKAGE, null );
		for( int i = 0; i < packageList.length; i++ )
		{
			XMIElement e = new XMIElement( packageList[ i ] );
			Namespace parentNamespace = parent; 
			Namespace nsp = createNamespace( parent == null ? null : parent.getId(), e.getId(), e.getName(), null, userId, null, userId );
			namespaces.put( nsp.getName(), nsp );
			parentNamespace = nsp;
			addClasses( nsp, e );
			addNamespaces( e, parentNamespace );
		}
	}
	
	private void addClasses( Namespace nsp, XMIElement e ) throws OMSMetaDataException
	{
		Node[] classList = e.getFromFirstLevel( EL_CLASS, EL_PACKAGE );
		for( int i = 0; i < classList.length; i++ )
		{
			XMIElement cls = new XMIElement( classList[ i ] );
			Scheme s = nsp.createScheme( null, cls.getId(), cls.getName(), isAbstract( cls ), null, userId, null, userId );
			schemes.put( cls.getId(), s );
			schemeElements.put( cls.getId(), cls );
			if( s.getName().equals( SCHEME_OBJECT ) && s.getNamespace().getPath().equals( "/" + NAMESPACE_OMSCUBE ) )
			{
				objectScheme = s;
			}
		}
	}
	
	private boolean isAbstract( XMIElement e )
	{
		return "true".equals( getAttribute( e.node, "isAbstract" ).getNodeValue() );
	}
	
	private void addDatatypes( Node[] packages ) throws OMSMetaDataException
	{
		for (int k = 0; k < packages.length; k++)
		{
			XMIElement element = new XMIElement( packages[ k ] );
			Node[] datatypesList = element.getFromFirstLevel( EL_CLASS, EL_PACKAGE );
			for( int i = 0; i < datatypesList.length; i++ )
			{
				XMIElement datatype = new XMIElement( datatypesList[ i ] );
				ArrayList enums = new ArrayList();
				String typeName = getSimpleType( datatype, enums );
				Namespace nsp = getNamespace( element.getName() );
				Datatype dt = nsp.createDatatype( typeName, datatype.getId(), datatype.getName(), /* TODO get from xml */ false, null, userId, null, userId );
				datatypes.put( datatype.getName(), dt );
				for( int j = 0; j < enums.size(); j++ )
				{
					XMIElement enumeration = (XMIElement) enums.get( j );
					char use = getUse( enumeration );
					DatatypeEnumeration en = dt.createDatatypeEnumeration( use, enumeration.getId(), enumeration.getName(), null, userId, null, userId );
				}
			}
			// process subpackages
			addDatatypes( element.getFromFirstLevel( EL_PACKAGE, null ) );
		} 
	}
	
	private String getSimpleType( XMIElement e, ArrayList en )
	{
		Node n = e.getByAttribute( "UML:Attribute", null, "name", "simple" );
		if( n == null )
		{
			n = e.getByAttribute( "UML:Attribute", null, "name", "default" );
		}
		if( n == null )
		{
			n = e.getByAttribute( "UML:Attribute", null, "name", null );
		}
		Node[] ns = e.getFromFirstLevel( "UML:Attribute", null );
		for( int i = 0; i < ns.length; i++ )
		{
			en.add( new XMIElement( ns[ i ] ) );
		}
		n = new XMIElement( n ).getByAttribute( "UML:TaggedValue", null, "tag", "type"  );
		return n.getAttributes().getNamedItem( "value" ).getNodeValue();
	}
	
	private char getUse( XMIElement e )
	{
		// TODO: implementation
		return '0';
	}
	
	
	private XMIElement getSchemesPackage( Document doc ) throws OMSMetaDataException
	{
		NodeList packages = doc.getElementsByTagName( EL_PACKAGE );
		for (int i = 0; packages != null && i < packages.getLength(); i++)
		{
			XMIElement pck = new XMIElement( packages.item( i ) );
			{
				if( SCHEMES_ELEMENT.equals( pck.getName() ) )
				{
					return pck;
				}
			}
		}
		throw new OMSMetaDataException( "The '" + SCHEMES_ELEMENT + "' package not found!" );
	}
	
	private XMIElement getDatatypesPackage( Document doc ) throws OMSMetaDataException
	{
		NodeList packages = doc.getElementsByTagName( EL_PACKAGE );
		for (int i = 0; packages != null && i < packages.getLength(); i++)
		{
			XMIElement pck = new XMIElement( packages.item( i ) );
			{
				if( DATATYPES_ELEMENT.equals( pck.getName() ) )
				{
					return pck;
				}
			}
		}
		throw new OMSMetaDataException( "The '" + DATATYPES_ELEMENT + "' package not found!" );
	}

	private Node getAttribute( Node n, String attName )
	{
		return n.getAttributes().getNamedItem( attName );
	}
	
	private Namespace getNamespace( String name ) throws OMSMetaDataException
	{
		Iterator it = namespaces.keySet().iterator();
		while( it.hasNext() )
		{
			String n = (String) it.next();
			if( n.equals( name ) )
			{
				return (Namespace) namespaces.get( name );
			}
		}
		
		throw new OMSMetaDataException( "Namespace '" + name + "' not found" );
	}
}
